package com.inns.position.controller;

import com.alibaba.fastjson.JSONArray;
import com.inns.position.exceptioin.InnsException;
import com.inns.position.exceptioin.InnsExceptionEnum;
import com.inns.position.hbase.HbaseConsts;
import com.inns.position.hbase.StatType;
import com.inns.position.hbase.config.properties.InnsProperties;
import com.inns.position.hbase.service.StatisticsService;
import com.inns.position.hbase.util.StatisticsUtil;
import com.inns.position.kafka.bean.LocationRealTimeInfo;
import com.inns.position.kafka.bean.TimePair;
import com.inns.position.redis.service.RealTimeDataService;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.util.Bytes;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class ApiController extends ApiConsts {

    @Resource
    private RealTimeDataService realTimeDataService;

    @Resource
    private StatisticsService statisticsService;

    @Resource
    private InnsProperties innsProperties;

    /**
     * 3.4.1.1	全量用户实时位置请求
     *
     * @param appId   APP服务器名称
     * @param buildId 营业厅
     * @return 全量用户的实时位置
     */
    @GetMapping("getAllPositionInfoByBuildId")
    public Object getAllPositionInfoByBuildId(@RequestParam(name = APPID) String appId,
                                              @RequestParam(name = BUILD_ID) String buildId) {
        checkAppId(appId);

        List<Map<String, String>> allPositionInfoByBuildId = realTimeDataService.getAllPositionInfoByBuildId(buildId);
        Map<String, Object> result = new HashMap<>();
        result.put(APPID, appId);
        result.put(BUILD_ID, buildId);
        result.put(USR_NO, allPositionInfoByBuildId.size());
        result.put(USR_LIST, allPositionInfoByBuildId);
        return result;
    }

    /**
     * 3.4.1.2	单用户实时位置信息求情
     *
     * @param appId   APP服务器名称
     * @param buildId 营业厅
     * @param imsi    IMSI
     * @return 返回用户当前所在楼宇、楼层及位置信息
     */
    @GetMapping("getPositionInfoByIMSI")
    public Object getPositionInfoByIMSI(@RequestParam(name = APPID) String appId,
                                        @RequestParam(name = BUILD_ID) String buildId,
                                        @RequestParam(name = IMSI) String imsi) {

        checkAppId(appId);

        Map<String, String> map = realTimeDataService.getPositionInfoByIMSI(buildId, imsi);
        map.put(APPID, appId);
        return map;
    }

    /**
     * 3.4.1.3	实时营业厅用户数据统计
     *
     * @param appId   APP服务器名称
     * @param buildId 营业厅
     * @return 返回营业厅的实时客流，今日累计客流，新增客户，平均驻留时长。
     */
    @GetMapping("getStatisticsInfoByBuildId")
    public Object getStatisticsInfoByBuildId(@RequestParam(name = APPID) String appId,
                                             @RequestParam(name = BUILD_ID) String buildId) {

        checkAppId(appId);

        Map<String, String> map = realTimeDataService.getStatisticsInfoByBuildId(buildId);
        map.put(APPID, appId);
        return map;
    }

    /**
     * 3.4.2.1	营业厅用户数据统计接口
     * <p>
     * 参数：
     *
     * @param appId    APP服务器名称
     * @param buildId  营业厅
     * @param statType 5分钟/小时/天/周/月  0:分，1：小时，2：天，3：周，4：月
     * @param statDate 时间戳
     * @return 返回指定时间粒度、指定时间范围内的营业厅用户统计数据。
     */
    @GetMapping("getStatUsrMsgBuild")
    public Object getStatUsrMsgBuild(@RequestParam(name = APPID) String appId,
                                     @RequestParam(name = BUILD_ID) String buildId,
                                     @RequestParam(name = STAT_TYPE) int statType,
                                     @RequestParam(name = STAT_DATE) long statDate) {

        checkAppId(appId);
        checkTimeType(statType, statDate, StatType.MINUTE);

        StatType type = StatType.valueOf(statType);
        String rowKey = StatisticsUtil.getRowKey(type, buildId, type.getEndTime(statDate));
        List<Cell> cells = statisticsService.get(HbaseConsts.TableName_Statistics_BuildId, HbaseConsts.FamilyColumn, rowKey, null);
        Cell cell = null;
        if (statType >= StatType.WEEK.getType()) {
            List<Cell> results = statisticsService.get(HbaseConsts.TableName_Statistics_Frequency_Frag_BuildId, HbaseConsts.FamilyColumn, rowKey, "results");
            if (results != null && results.size() > 1) {
                cell = results.get(0);
            }
        }
        return getStringStringMap(appId, buildId, cells, null, statType, statDate, cell);
    }

    /**
     * 3.4.2.2	POI用户统计接口
     * <p>
     * * @param appId   APP服务器名称
     *
     * @param buildId  营业厅
     * @param poiId    POI唯一标识
     * @param statType 5分钟/小时/天/周/月  0:分，1：小时，2：天，3：周，4：月
     * @param statDate 时间戳
     * @return 返回指定时间粒度、指定时间范围内的POI的用户数据。
     */
    @GetMapping("getStatUsrMsgPoi")
    public Object getStatUsrMsgPoi(@RequestParam(name = APPID) String appId,
                                   @RequestParam(name = BUILD_ID) String buildId,
                                   @RequestParam(name = POI_ID) String poiId,
                                   @RequestParam(name = STAT_TYPE) int statType,
                                   @RequestParam(name = STAT_DATE) long statDate) {

        checkAppId(appId);
        checkTimeType(statType, statDate, StatType.HOUR);

        StatType type = StatType.valueOf(statType);
        String rowKey = StatisticsUtil.getRowKey(type, buildId + "|" + poiId, type.getEndTime(statDate));
        List<Cell> cells = statisticsService.get(HbaseConsts.TableName_Statistics_POI, HbaseConsts.FamilyColumn, rowKey, null);
        Cell cell = null;
        if (statType >= StatType.WEEK.getType()) {
            List<Cell> results = statisticsService.get(HbaseConsts.TableName_Statistics_Frequency_Frag_POI, HbaseConsts.FamilyColumn, rowKey, "results");
            if (results != null && results.size() > 1) {
                cell = results.get(0);
            }
        }
        return getStringStringMap(appId, buildId, cells, poiId, statType, statDate, cell);
    }

    private Map<String, String> getStringStringMap(String appId, String buildId, List<Cell> cells, String poiId, int statType, long statDate, Cell cell) {
        Map<String, String> result = new HashMap<>();
        result.put(APPID, appId);
        result.put(BUILD_ID, buildId);
        result.put(STAT_TYPE, statType + "");
        result.put(STAT_DATE, statDate + "");
        if (poiId != null) {
            result.put(POI_ID, poiId);
        }
        if (null != cells) {
            Map<String, String> keyToKey = new HashMap<>();
            keyToKey.put("count", ACC_USR_CNT);// 累计用户数	5分钟/小时/天/周/月均可获此指标
            if (statType >= StatType.HOUR.getType()) {
                keyToKey.put("newCount", NEW_USR_CNT);// 新增用户数	小时/天/周/月可获此指标
                keyToKey.put("totalTime", RESIDE_DUR);// 总驻留时长	小时/天/周/月可获此指标
            }
            StatisticsUtil.fillResultMap(cells, result, keyToKey);
            try {
                long t = Long.parseLong(result.get(RESIDE_DUR));
                long c = Long.parseLong(result.get(ACC_USR_CNT));
                long avg_reside_dur = 0;
                if(c!=0) {//这里会有c==0的情况
                    avg_reside_dur = t / c;
                }
                result.put(AVG_RESIDE_DUR, avg_reside_dur + "");// 平均驻留时长	小时/天/周/月可获此指标
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }

            if (cell != null) {
                String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                result.put(ACCESS_FRE_CNT, value);// 访问频次	周/月可获此指标
            }
        } else {
            result.put(ACC_USR_CNT, "0");
            result.put(NEW_USR_CNT, "0");
            result.put(RESIDE_DUR, "0");
            result.put(AVG_RESIDE_DUR, "0");
        }
        return result;
    }

    /**
     * 3.4.2.3	营业厅用户数据明细接口
     * <p>
     * * @param appId   APP服务器名称
     *
     * @param buildId  营业厅
     * @param statType 5分钟/小时/天/周/月  0:分，1：小时，2：天，3：周，4：月
     * @param statDate 时间戳
     * @return 返回指定时间粒度、指定时间范围内的营业厅详细用户数据
     */
    @GetMapping("getUsrMsgBuild")
    public Object getUsrMsgBuild(@RequestParam(name = APPID) String appId,
                                 @RequestParam(name = BUILD_ID) String buildId,
                                 @RequestParam(name = STAT_TYPE) int statType,
                                 @RequestParam(name = STAT_DATE) long statDate) {

        checkAppId(appId);
        checkTimeType(statType, statDate, StatType.HOUR);

        StatType type = StatType.valueOf(statType);
        String rowKey = StatisticsUtil.getRowKey(type, buildId, type.getEndTime(statDate));
        List<Cell> cells = statisticsService.get(HbaseConsts.TableName_Statistics_BuildId, HbaseConsts.FamilyColumn, rowKey, null);
        return getStringStringMap2(appId, buildId, cells, null, statType, statDate);
    }

    /**
     * 3.4.2.4	POI用户详细用户接口
     * <p>
     * * @param appId  APP服务器名称
     *
     * @param buildId  营业厅
     * @param poiId    POI唯一标识
     * @param statType 5分钟/小时/天/周/月  0:分，1：小时，2：天，3：周，4：月
     * @param statDate 时间戳
     * @return 返回指定时间粒度、指定时间范围内的POI详细用户数据。
     */
    @GetMapping("getUsrMsgPoi")
    public Object getUsrMsgPoi(@RequestParam(name = APPID) String appId,
                               @RequestParam(name = BUILD_ID) String buildId,
                               @RequestParam(name = POI_ID) String poiId,
                               @RequestParam(name = STAT_TYPE) int statType,
                               @RequestParam(name = STAT_DATE) long statDate) {

        checkAppId(appId);
        checkTimeType(statType, statDate, StatType.HOUR);

        StatType type = StatType.valueOf(statType);
        String rowKey = StatisticsUtil.getRowKey(type, buildId + "|" + poiId, type.getEndTime(statDate));
        List<Cell> cells = statisticsService.get(HbaseConsts.TableName_Statistics_POI, HbaseConsts.FamilyColumn, rowKey, null);
        return getStringStringMap2(appId, buildId, cells, poiId, statType, statDate);
    }

    private Map<String, String> getStringStringMap2(String appId, String buildId, List<Cell> cells, String poiId, int statType, long statDate) {
        Map<String, String> result = new HashMap<>();
        result.put(APPID, appId);
        result.put(BUILD_ID, buildId);
        result.put(STAT_TYPE, statType + "");
        result.put(STAT_DATE, statDate + "");
        if (poiId != null) {
            result.put(POI_ID, poiId);
        }
        if (null != cells) {
            Map<String, String> keyToKey = new HashMap<>();
            keyToKey.put("count", ACC_USR_CNT);
            keyToKey.put("imsiSet", USR_LIST);
            StatisticsUtil.fillResultMap(cells, result, keyToKey);
        } else {
            result.put(ACC_USR_CNT, "0");
        }
        return result;
    }

    /**
     * 3.4.2.5	用户详细数据接口
     *
     * @param appId    APP服务器名称
     * @param buildId  营业厅
     * @param poiId    POI唯一标识 TODO 该参数在此接口好像没有意义
     * @param statType 5分钟/小时/天/周/月  0:分，1：小时，2：天，3：周，4：月
     * @param statDate 时间戳
     * @param imsis    IMSI列表
     * @return 返回指定时间粒度、指定时间范围内的POI详细用户数据
     */
    @GetMapping("getUsrMsg")
    public Object getUsrMsg(@RequestParam(name = APPID) String appId,
                            @RequestParam(name = BUILD_ID) String buildId,
                            @RequestParam(name = POI_ID, required = false) String poiId,
                            @RequestParam(name = STAT_TYPE) int statType,
                            @RequestParam(name = STAT_DATE) long statDate,
                            String imsis) {

        checkAppId(appId);
        checkTimeType(statType, statDate, StatType.HOUR);

        List<String> imsiList = JSONArray.parseArray(imsis, String.class);
        if (imsiList == null || imsiList.size() == 0) {
            throw new RuntimeException("getUsrMsg 参数错误，imsis 不能为空！");
        }

        Map<String, Object> result = new HashMap<>();
        result.put(APPID, appId);
        result.put(BUILD_ID, buildId);
        result.put(STAT_TYPE, statType + "");
        result.put(STAT_DATE, statDate + "");


        StatType type = StatType.valueOf(statType);
        long endTime = type.getEndTime(statDate);

        List<Map<String, Object>> users = new ArrayList<>();

        Map<String, String> keyToKey = new HashMap<>();
        keyToKey.put("poiId", POI_ID);
        keyToKey.put("totalTime", RESIDE_DUR);
        keyToKey.put("num", ACC_USR_CNT);

        for (String imsi : imsiList) {
            String regexKey = "^" + type.getType() + "\\|" + buildId + "\\|" + imsi + "\\|" + ".*" + "\\|" + (endTime / 1000) + "$";
            List<List<Cell>> lists = statisticsService.scanRegexRowKey(HbaseConsts.TableName_Statistics_User, regexKey);
            long sumDur = 0;
            int sumCnt = 0;
            List<Map<String, String>> poiList = new ArrayList<>();
            for (List<Cell> cells : lists) {
                Map<String, String> resultMap = new HashMap<>();
                StatisticsUtil.fillResultMap(cells, resultMap, keyToKey);
                sumDur += Long.parseLong(resultMap.get(RESIDE_DUR));//这里的RESIDE_DUR线上是15189770000。超长了，转换的时候报错了。
                if(Long.parseLong(resultMap.get(RESIDE_DUR)) != 0L) {
                    sumCnt += Integer.parseInt(resultMap.get(ACC_USR_CNT));
                    poiList.add(resultMap);
                }
            }
            Map<String, Object> userMap = new HashMap<>();
            userMap.put(IMSI, imsi);
            userMap.put(RESIDE_DUR, sumDur);
            userMap.put(ACC_USR_CNT, sumCnt);
            //加入初次访问时间。 此imsi访问这个build_id的最初时间
            LocationRealTimeInfo locationRealTimeInfo =  realTimeDataService.getRealTimeInfoByIMSI(buildId, imsi);
            if (locationRealTimeInfo == null) {
                userMap.put(ApiConsts.FIRST_DATE, System.currentTimeMillis() + "");
            } else {
                List<TimePair> accessTimes = locationRealTimeInfo.getAllAccessTimes();
                userMap.put(ApiConsts.FIRST_DATE, accessTimes.get(0).getStartTime() + "");
            }

            //加入初次访问时间结束
            if(sumDur != 0) {
                userMap.put("poiList", poiList);
                users.add(userMap);
            }

        }
        result.put(ACC_USR_CNT, users.size() + "");
        result.put("users", users);
        return result;
    }

    /**
     * 3.4.2.6	用户轨迹接口
     *
     * @param appId    APP服务器名称
     * @param buildId  营业厅
     * @param imsi     用户唯一标识
     * @param statType 5分钟/小时/天/周/月 0:分，1：小时，2：天，3：周，4：月
     * @param statDate 时间戳
     * @return 返回指定时间粒度、指定时间范围内的营业厅详细用户数据
     */
    @GetMapping("getUsrPath")
    public Object getUsrPath(@RequestParam(name = APPID) String appId,
                             @RequestParam(name = BUILD_ID) String buildId,
                             @RequestParam(name = IMSI) String imsi,
                             @RequestParam(name = STAT_TYPE) int statType,
                             @RequestParam(name = STAT_DATE) long statDate) {

        checkAppId(appId);
//        checkTimeType(statType, statDate, StatType.DAY);
        checkTimeType(statType, statDate, StatType.HOUR);
        if (statType > StatType.DAY.getType()) {
            throw new InnsException(InnsExceptionEnum.TIME_TYPE_ERROR);
        }

        Map<String, String> result = new HashMap<>();
        result.put(APPID, appId);
        result.put(BUILD_ID, buildId);
        result.put(IMSI, imsi);
        result.put(STAT_TYPE, statType + "");
        result.put(STAT_DATE, statDate + "");

        LocationRealTimeInfo info = realTimeDataService.getRealTimeInfoByIMSI(buildId, imsi);
        if (info == null) {
            throw new InnsException(InnsExceptionEnum.USER_NOT_FIND);
        }
        long startTime = StatType.DAY.getStartTime(statDate);
        long endTime = StatType.DAY.getEndTime(statDate);
        long durTime = 0;
        List<TimePair> allAccessTimes = info.getAccessTimesByStartTime(startTime);
        if (allAccessTimes.size() == 0) {
            throw new RuntimeException("getUsrPath 参数错误！");
        }
        for (TimePair timePair : allAccessTimes) {
            if (timePair.getStartTime() < endTime) {
                durTime += timePair.getDurTime();
            }
        }
        result.put("day_first_date", allAccessTimes.get(0).getStartTime() + "");
        result.put(RESIDE_DUR, durTime + "");
        result.put("Miles", "0");// TODO 不知道算法，客户端可以做

        StatType type = StatType.valueOf(statType);
        String rowKey = StatisticsUtil.getRowKey(type, buildId + "|" + imsi, type.getEndTime(statDate));
        List<Cell> results = statisticsService.get(HbaseConsts.TableName_Statistics_Path, HbaseConsts.FamilyColumn, rowKey, "results");
        Cell cell = results.get(0);
        String value = Bytes.toString(results.get(0).getValueArray(), results.get(0).getValueOffset(), cell.getValueLength());
        result.put("paths", value);
        return result;
    }

    /**
     * 3.4.2.7	营业厅/POI驻留时长分段统计接口
     *
     * @param appId    APP服务器名称
     * @param buildId  营业厅
     * @param poiId    POI唯一标识
     * @param statType 天/周/月
     * @param statDate 时间戳
     * @return 返回营业厅/POI在指定时间粒度下用户的驻留时长分段统计值
     */
    @GetMapping("getStatAccessTime")
    public Object getStatAccessTime(@RequestParam(name = APPID) String appId,
                                    @RequestParam(name = BUILD_ID) String buildId,
                                    @RequestParam(name = POI_ID, required = false) String poiId,
                                    @RequestParam(name = STAT_TYPE) int statType,
                                    @RequestParam(name = STAT_DATE) long statDate) {

        checkAppId(appId);
        checkTimeType(statType, statDate, StatType.DAY);

        Map<String, String> result = new HashMap<>();
        result.put(APPID, appId);
        result.put(BUILD_ID, buildId);
        result.put(STAT_TYPE, statType + "");
        result.put(STAT_DATE, statDate + "");

        StatType type = StatType.valueOf(statType);
        String rowKey = StatisticsUtil.getRowKey(type, buildId, type.getEndTime(statDate));
        String tableName = HbaseConsts.TableName_Statistics_Duration_Frag_BuildId;
        if (poiId != null) {
            rowKey = StatisticsUtil.getRowKey(type, buildId + "|" + poiId, type.getEndTime(statDate));
            result.put(POI_ID, poiId);
            tableName = HbaseConsts.TableName_Statistics_Duration_Frag_POI;
        }
        if (statType >= StatType.DAY.getType()) {
            List<Cell> results = statisticsService.get(tableName, HbaseConsts.FamilyColumn, rowKey, "results");
            if (results != null && results.size() > 0) {
                Cell cell = results.get(0);
                String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                result.put(ACCESS_FRE_CNT, value);

                List<Cell> intervals = statisticsService.get(tableName, HbaseConsts.FamilyColumn, rowKey, "intervals");
                cell = intervals.get(0);
                value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                result.put("intervals", value);
            }
        }
        return result;
    }

    /**
     * 3.4.2.8	营业厅/POI访问频次统计
     *
     * @param appId    APP服务器名称
     * @param buildId  营业厅
     * @param poiId    POI唯一标识
     * @param statType 时间范围 周/月
     * @param statDate 时间范围
     * @return 返回营业厅/POI在指定时间粒度下用户的访问频次统计值。
     */
    @GetMapping("getStatAccessCount")
    public Object getStatAccessCount(@RequestParam(name = APPID) String appId,
                                     @RequestParam(name = BUILD_ID) String buildId,
                                     @RequestParam(name = POI_ID, required = false) String poiId,
                                     @RequestParam(name = STAT_TYPE) int statType,
                                     @RequestParam(name = STAT_DATE) long statDate) {

        checkAppId(appId);
        checkTimeType(statType, statDate, StatType.WEEK);

        Map<String, String> result = new HashMap<>();
        result.put(APPID, appId);
        result.put(BUILD_ID, buildId);
        result.put(STAT_TYPE, statType + "");
        result.put(STAT_DATE, statDate + "");

        StatType type = StatType.valueOf(statType);
        String rowKey = StatisticsUtil.getRowKey(type, buildId, type.getEndTime(statDate));
        String tableName = HbaseConsts.TableName_Statistics_Frequency_Frag_BuildId;
        if (poiId != null) {
            rowKey = StatisticsUtil.getRowKey(type, buildId + "|" + poiId, type.getEndTime(statDate));
            result.put(POI_ID, poiId);
            tableName = HbaseConsts.TableName_Statistics_Frequency_Frag_POI;
        }
        if (statType >= StatType.WEEK.getType()) {
            List<Cell> results = statisticsService.get(tableName, HbaseConsts.FamilyColumn, rowKey, "results");
            if (results != null && results.size() > 0) {
                Cell cell = results.get(0);
                String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                result.put(ACCESS_FRE_CNT, value);
            }
        }
        return result;
    }

    private void checkAppId(String appId) {
        if (!innsProperties.getAppId().equals(appId)) {
            throw new InnsException(InnsExceptionEnum.APP_ID_ERROR);
        }
    }

    private void checkTimeType(int statType, long statDate, StatType smallestType) {
        if (statType < smallestType.getType() || statType > StatType.MONTH.getType()) {
            throw new InnsException(InnsExceptionEnum.TIME_TYPE_ERROR);
        }
    }
}
